/*
 * Copyright 2010-2012 Susanta Tewari. <freecode4susant@users.sourceforge.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package coalescent.is;

import coalescent.Genealogy;
import coalescent.model.K69;
import coalescent.model.PopGenModel;
import coalescent.statistic.AC;
import com.google.common.base.Function;
import commons.is.Factor;
import commons.is.Proposal;
import commons.is.Proposal_w_Prob;
import commons.is.Sampler;
import commons.util.FormatUtil;
import org.junit.Assert;

import java.math.BigDecimal;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Test framework class for testing g-proposals on population genetic models. Sub-classes should
 * provide data and delegate tests to overloaded methods {@code test_GeneTreeProposal}.
 *
 * @param <S> type extends {@code AC}
 * @param <M> type extends {@code PopGenModel}
 *
 * @version version
 * @since 1.4.0
 * @author Susanta Tewari
 * @history First: 09/10/2012.
 */
public class GProposalTestBase<S extends AC<S, M>, M extends PopGenModel> {

    private final K69 k69 = new K69(1);

    /** identity target function */
    private final Function<Genealogy<S, M>, BigDecimal> idTF = new Function<Genealogy<S, M>,
                                                                   BigDecimal>() {

        @Override
        public BigDecimal apply(final Genealogy<S, M> f) {
            return BigDecimal.ONE;
        }
    };
    private final S statistic;
    private final BigDecimal probability;
    private final String dataName;

    public GProposalTestBase(final String dataName, final S statistic,
                             final BigDecimal probability) {

        this.dataName    = dataName;
        this.statistic   = statistic;
        this.probability = probability;
    }

    public S getStatistic() {
        return statistic;
    }

    public K69 getK69() {
        return k69;
    }

    /**
     * Tests by checking the average IS estimate. P value is reported.
     *
     * @param g_proposal proposal distribution
     * @param name proposal name (to be used in logging)
     */
    protected void test_GeneTreeProposal(final Proposal_w_Prob<Genealogy<S, M>> g_proposal,
            final String name) {
        test_GeneTreeProposal(GFactors.of(g_proposal), g_proposal, name);
    }

    /**
     * Tests by checking the average IS estimate. P value is reported.
     *
     * @param factor is-factor
     * @param g_proposal proposal distribution
     * @param name proposal name (to be used in logging)
     */
    protected void test_GeneTreeProposal(final Factor<Genealogy<S, M>> factor,
            final Proposal<Genealogy<S, M>> g_proposal, final String name) {


        // run sampler
        final int SAMPLE_SIZE                  = 30;
        final Sampler<Genealogy<S, M>> sampler = new Sampler<>(name, g_proposal, factor, idTF);

        sampler.setIteratorBySampleSize(SAMPLE_SIZE);

        try {
            sampler.run();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }


        // get IS estimate, SE & ESS
        final BigDecimal isEstimate = sampler.nowMean();
        final BigDecimal isSE       = sampler.nowStdError();

        if (isSE.equals(BigDecimal.ZERO)) {

            Assert.assertEquals(probability.doubleValue(), isEstimate.doubleValue(), 1.0E-10);

            return;
        }

        final BigDecimal isESS = sampler.nowESS();


        // checks inequality ABS(Exact-mean) <= Z * Est-Std-Dev (z=3 should pass the test)
        final BigDecimal LHS = probability.subtract(isEstimate).abs();
        final BigDecimal Z   = new BigDecimal(3.0);
        final BigDecimal RHS = isSE.multiply(Z);


        // format exact prob
        final String fmtExact = FormatUtil.format(probability);
        final String fmtLHS   = FormatUtil.format(LHS);
        final String fmtRHS   = FormatUtil.format(RHS);


        // format IS estimate average from statistics
        final String fmtISAverageEstimate = FormatUtil.format(isEstimate);
        Level level                       = Level.SEVERE;
        final boolean passed              = LHS.compareTo(RHS) <= 0;

        if (passed) {
            level = Level.INFO;
        }

        final Logger LOGGER = Logger.getLogger(getClass().getName());


        // log
        LOGGER.log(
            level,
            "Proposal: {0} LHS(abs-diff): {1} RHS(Z={2}): {3} Passed: {4} Test-Sample-Size {4} "
            + "IS-Sample-Size: {5} Exact value: {6} IS Average Estimate: {7} Data {8}", new Object[] {
            name, fmtLHS, Z, fmtRHS, passed, SAMPLE_SIZE, fmtExact, fmtISAverageEstimate, dataName
        });


        // assert
        Assert.assertTrue(passed);    // pass if p-value > ALPHA
    }
}
